/**
 *  EKology - An Ecosystem Simulation Framework
 *  Copyright (C) 2008  Aarón Tavío - aaron.tavio at gmail.com - ULPGC
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package net.ekology.ekosystem;

import net.ekology.core.datatypes.Attribute;
import net.ekology.core.datatypes.SignalData;
import net.ekology.core.datatypes.ValueData;
import net.ekology.ekosystem.datatypes.*;
import net.ekology.graphics.*;

import java.util.Calendar;
import java.util.Hashtable;
import java.util.GregorianCalendar;
import java.awt.Graphics2D;
import java.io.Serializable;


/**
 * Every biotope must extend this class
 * <p>
 * This class provides numerous functionalities to biotopes
 * 
 * @author Aarón Tavío - aaron.tavio at gmail.com
 * @version 1.0.0 - 20081019-1700
 */
public abstract class Biotope implements Drawable, Serializable {
    private String sID;
    protected EKosystem oEKosystem;
    private Calendar oLocalDate;
    private Hashtable<String,Attribute> htAttribute;
    private int iLoopTime;
    private Space oSpace;
    
    

    public Biotope() {
        this.sID = null;
        this.oEKosystem = null;
        this.oLocalDate = new GregorianCalendar();
        this.htAttribute = new Hashtable<String,Attribute>();
        iLoopTime = 0;
        this.oSpace = null;
    }
    
    public Biotope(String sID, double[] aDimension, EKosystem oEKosystem) {
        this.sID = sID;
        this.oEKosystem = oEKosystem;
        this.oLocalDate = new GregorianCalendar();
        this.htAttribute = new Hashtable<String,Attribute>();
        this.iLoopTime = 0;
        this.oSpace = new Space(aDimension);
    }
    
    public Biotope(String sID, double[] aDimension, EKosystem oEKosystem, Hashtable htArgument) {
        this.sID = sID;
        this.oEKosystem = oEKosystem;
        this.oLocalDate = new GregorianCalendar();
        this.htAttribute = new Hashtable<String,Attribute>(htArgument);
        this.oSpace = new Space(aDimension);
    }
    
    public String getID() {
        return this.sID;
    }
    
    public void setID(String sID) {
        if (this.sID==null) this.sID = sID;
    }
    
    public void setEKosystem(EKosystem oEKosystem) {
        if (this.oEKosystem == null) this.oEKosystem = oEKosystem;
    }
    
    public Space getSpace() {
        return this.oSpace.clone();
    }
    
    public void setSpace(Space oSpace) {
        if (this.oSpace == null) this.oSpace = oSpace;
    }
    
    
    public Calendar getLocalDate() {
        return this.oLocalDate;
    }
    
    public void addElapsedTime(int iMiliseconds) {
        long lTimestamp;
        
        iLoopTime = iMiliseconds;
        
        lTimestamp = oLocalDate.getTimeInMillis();
        lTimestamp += iMiliseconds;
        oLocalDate.setTimeInMillis(lTimestamp);
    }
    
    
    public int getLoopTime() {
        return iLoopTime;
    }
    
    public int getAgentCount() {
        return oEKosystem.getAgentCount();
    }
    
    
    /**
     * Gets the attribute names provided by the developer
     * <p>
     * Used in conjunction with <code>getAttributes()</code>, <code>getAttribute()</code>
     * and <code>setAttribute()</code>. Must be overriden.
     * 
     * @return an array of names
     */
    public String[] getAttributeNames() {
        // must be overriden
        return (String[]) null;
    }
    
    
    /**
     * Gets the attributes provided by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code>, <code>getAttribute()</code>
     * and <code>setAttribute()</code>. Must be overriden.
     * 
     * @return an array of Attributes
     */
    public Attribute[] getAttributes() {
        // must be overriden
        return (Attribute[]) null;
    }
    
   
    /**
     * Gets an <code>Attribute</code> defined by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code>, <code>getAttributes()</code>
     * and <code>setAttribute()</code>. Must be overriden.
     * 
     * @param sName the desired attribute's name
     * @return the <code>Attribute</code>
     */
    public Attribute getAttribute(String sName) {
        // must be overriden
        return (Attribute) null;
    }
    
    
    /**
     * Sets an <code>Attribute</code> defined by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code> and <code>getAttributes()</code>.
     * Must be overriden.
     * 
     * @param oAttribute 
     */
    public void setAttribute(Attribute oAttribute) {
        // must be overriden
    }
    
    
    /**
     * Sets an <code>Attribute</code> defined by the developer
     * <p>
     * Used in conjunction with <code>getAttributeNames()</code> and <code>getAttributes()</code>.
     * Must be overriden.
     * 
     * @param sName the desired attribute's name
     * @param sValue the string representation of the value
     */
    public void setAttribute(String sName, String sValue) {
        // must be overriden
    }
    
    
    /**
     * Gets an agent's position
     * 
     * @param sID the agent identifier
     * @return the position vector
     */
    public EKVector getPosition(String sID) {
        return (EKVector) oEKosystem.getAgent(sID).getPosition();
    }
    
    
    /**
     * Sets the agent's position
     * 
     * @param sID the agent identifier
     * @param oPosition the position vector
     */
    public void setPosition(String sID, EKVector oPosition) {
        oEKosystem.getAgent(sID).setPosition(oPosition);
    }
    
    
    /**
     * Gets the agent's orientation
     * 
     * @param sID the agent identifier
     * @return the orientation vector
     */
    public EKVector getOrientation(String sID) {
        return (EKVector) oEKosystem.getAgent(sID).getOrientation();
    }
    
    
    /**
     * Sets the agent's orientation
     * 
     * @param sID the agent identifier
     * @param oOrientation the orientation vector
     */
    public void setOrientation(String sID, EKVector oOrientation) {
        oEKosystem.getAgent(sID).setOrientation(oOrientation);
    }
    
    public void submitEvent(EKEvent oEvent) {
        oEKosystem.addEvent(oEvent);
    }
    
    
    /**
     * Invoked right after the biotope is created.
     * <p>
     * To use this functionality, this method must be overriden.
     */
    public void initialize() {
        // should be overriden
    }
    
    
    /**
     * Invoked right before EKology's main loop begins.
     * <p>
     * To use this functionality, this method must be overriden.
     */
    public void preprocess() {
        // should be overriden
    }
    
    
    /**
     * Invoked right after EKology's main loop begins.
     * <p>
     * To use this functionality, this method must be overriden.
     */
    public void postprocess() {
        // should be overriden
    }
    
    
    public void draw(Graphics2D g2) {
        for (Agent oAgent: oEKosystem.getAgents()) {
            oAgent.draw(g2);
        }
    }
    
    
    /**
     * Gets the signal names provided by the developer
     * <p>
     * Used in conjunction with <code>getSignalData()</code> and <code>getValueData()</code>
     * Must be overriden.
     * 
     * @return an array of names
     */
    public String[] getSignalNames() {
        // should be overriden
        return null;
    }
    
    
    /**
     * Gets the signal's data
     * <p>
     * Used in conjunction with <code>getSignalNames()</code> and <code>getValueData()</code>
     * Must be overriden.
     * 
     * @param sSignal the signal name
     * @return an object containig the data
     */
    public SignalData getSignalData(String sSignal) {
        // should be overriden
        return null;
    }
    
    
    /**
     * Gets the signal's value data
     * <p>
     * Used in conjunction with <code>getSignalNames()</code> and <code>getSignalData()</code>
     * Must be overriden.
     * 
     * @param sSignal the signal name
     * @return an object containig the data
     */
    public ValueData getValueData(String sSignal) {
        // should be overriden
        return null;
    }
    
    
    @Override
    public String toString() {
        return String.format("%s(%s): %s", getClass().getSimpleName(), getClass().getSuperclass().getSimpleName(), sID);
    }
    
    
    /**
     * Given an agent, adds triggered EKEvents invoking EKosystem.addEvent()
     * <p>
     * These are processed later by Automaton.step()
     * 
     * @param sAgentID the agent's identifier
     * @param sCurrentState agent's current state name
     */
    public abstract void checkState(String sAgentID, String sCurrentState);
    
}
